package com.abc.tacos.controller;

import com.abc.tacos.bean.Ingredient;
import com.abc.tacos.bean.Ingredient.Type;
import com.abc.tacos.bean.Order;
import com.abc.tacos.bean.Taco;
import com.abc.tacos.dao.IngredientRepository;
import com.abc.tacos.dao.TacoRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author Kar
 * @create 2022-04-13 下午4:38
 */
@Slf4j
@Controller
@SessionAttributes("order")
@RequestMapping("/design")
public class DesignTacoController {

    /**
     * 对于必需的依赖，建议使用基于构造函数的注入，设置它们为不可变的，并防止它们为null。对于可选的依赖项，建议使用基于sett的注入。
     */
    private final IngredientRepository ingredientRepository;

    private final TacoRepository designRepository;

    /**
     * 基于构造函数的依赖注入:
     * 1. @Autowired注解也是可以省去的。
     * 2. 基于构造函数注入的主要优点是可以将需要注入的字段声明为final， 使得它们会在类实例化期间被初始化，这对于所需的依赖项很方便。
     *
     * @param ingredientRepository
     * @param designRepository
     */
    public DesignTacoController(IngredientRepository ingredientRepository, TacoRepository designRepository) {
        this.ingredientRepository = ingredientRepository;
        this.designRepository = designRepository;
    }

    @ModelAttribute(name = "order")
    public Order order() {
        return new Order();
    }

    @ModelAttribute(name = "taco")
    public Taco taco() {
        return new Taco();
    }

    @ModelAttribute
    private void groupIngredients(Model model) {
        // 从数据库中获取Ingredient信息
        ArrayList<Ingredient> ingredients = new ArrayList<>();
        ingredientRepository.findAll().forEach(ingredients::add);

        Type[] types = Type.values();
        for (Type type : types) {
            // 对Ingredients分组
            model.addAttribute(
                    type.toString().toLowerCase(),
                    filterByType(ingredients, type));
        }
    }

    @GetMapping
    public String showDesignForm() {
        // model.addAttribute("design", new Taco());
        return "design";
    }

    /**
     * @param design 订单数据，spring默认为其绑定请求中的数据
     * @param errors 绑定过程中出现的格式匹配异常
     * @param order  使用@SessionAttribute防止spring绑定请求参数 
     *  * 书中指出使用@ModelAttribute就能防止spring绑定请求参数
     * @return 重定向至订单页面
     */
    @PostMapping
    public String processDesign(
            @Valid Taco design,
            Errors errors,
            @SessionAttribute Order order) {

        if (errors.hasErrors()) {
            return "design";
        }
        Taco saved = designRepository.save(design);
        // 保存到session域中
        order.addDesign(saved);
        return "redirect:/orders/current";
    }

    // abandoned
    // @ModelAttribute
    public void addIngredientsToModel(Model model) {
        List<Ingredient> ingredients = Arrays.asList(
                new Ingredient("FLTO", "Flour Tortilla", Type.WRAP),
                new Ingredient("COTO", "Corn Tortilla", Type.WRAP),
                new Ingredient("GRBF", "Ground Beef", Type.PROTEIN),
                new Ingredient("CARN", "Carnitas", Type.PROTEIN),
                new Ingredient("TMTO", "Diced Tomatoes", Type.VEGGIES),
                new Ingredient("LETC", "Lettuce", Type.VEGGIES),
                new Ingredient("CHED", "Cheddar", Type.CHEESE),
                new Ingredient("JACK", "Monterrey Jack", Type.CHEESE),
                new Ingredient("SLSA", "Salsa", Type.SAUCE),
                new Ingredient("SRCR", "Sour Cream", Type.SAUCE)
        );
        Type[] types = Ingredient.Type.values();
        for (Type type : types) {
            model.addAttribute(type.toString().toLowerCase(),
                    filterByType(ingredients, type));
        }
    }

    /**
     * 根据Type筛选出对应的Ingredient列表
     * @param ingredients
     * @param type
     * @return
     */
    private List<Ingredient> filterByType(List<Ingredient> ingredients, Type type) {
        return ingredients
                .stream()
                .filter(x -> x.getType().equals(type))
                .collect(Collectors.toList());
    }
}
